`timescale 1ns/1ps
`default_nettype none

/* NOTE:
*  - 将要显示的数据从SDRAM读取到缓冲区
*/

/* NOTE:
* 行控：
* - 同时打开3个bank进行处理（因为4个bank分别存放4个port的数据，但实际上编号为4的倍数的port都没有使用）
* - 三个bank轮流读取，这样连续的3个word共96bit，去除未用到的颜色剩下72bit，刚好可以存放到一个BRAM的2个连续地址空间（36bit*2）
*
* 列控：
* - 同时打开4个bank进行处理，对应pixel地址地位分别为0,1,2,3
* - 读取的时候，按照先port，后pixel的顺序（port数中是8的倍数）
* - 每个word包含8个port的数据，每4个word包含连续4个像素的8个port的数据
* - 将连续4个word按照port顺序拆分成8个12bit（去掉无效的color）
* - 将相同4个pixel的所有port数据中无效的port去掉
* - 将有效port的数据，每三个port一组36bit，存放到BRAM
*/

module pixel_reader (
    // system signal
    input  wire         I_sclk,  // 125M
    input  wire         I_sdram_clk, // 150M
    input  wire         I_rst_n,
    // config
    input  wire         I_cfg_scan_mode,    // 0 - 16扫，1 - 32扫
    input  wire [5:0]   I_cfg_color_sel,    // RGB选择
    input  wire [1:0]   I_cfg_box_dir,      // 箱体方向
    input  wire [7:0]   I_cfg_vport_num,    // 虚拟数据组数
    input  wire [31:0]  I_cfg_vport_mask,   // 虚拟数据组中有效组标识
    input  wire         I_cfg_force_en,     // 强制读出的第几bit为1，其余bit为0
    input  wire [4:0]   I_cfg_force_bit,    // 0:全为0; 1:只bit0为1; 2:只bit1为1; ...
    // read request
    input  wire         I_read_req,         // 读请求
    output wire         O_read_busy,        // 读忙碌
    input  wire [1:0]   I_read_buf_sel,     // 读取SDRAM分块地址
    input  wire [3:0]   I_read_bit_sel,     // 读取的bit选择
    input  wire [4:0]   I_read_scan_id,     // 读取的scan id
    input  wire [4:0]   I_read_port_max,    // 读取的最大port id
    input  wire [8:0]   I_read_pixel_id,    // 读取的起始像素id，总是8的倍数
    input  wire [9:0]   I_read_pixel_count, // 读取的像素数，总是8的倍数，最多512
    input  wire [7:0]   I_read_ram_addr,    // 存放到RAM的起始地址
    // scan map
    output wire         O_scan_map_rden,
    output wire [4:0]   O_scan_map_addr,
    input  wire [4:0]   I_scan_map_q,
    // ram interface
    output wire         O_ram_wclk,
    output wire [7:0]   O_ram_wren,
    output wire [7:0]   O_ram_addr,
    output wire [35:0]  O_ram_data,
    // sdram mux
    output wire         O_mux_req,
    input  wire         I_mux_ack,
    input  wire         I_mux_irq,
    output wire         O_mux_cs_n,
    output wire         O_mux_ras_n,
    output wire         O_mux_cas_n,
    output wire         O_mux_we_n,
    output wire [1:0]   O_mux_ba,
    output wire [10:0]  O_mux_addr,
    output wire [31:0]  O_mux_dq_out,
    input  wire [31:0]  I_mux_dq_in,
    output wire         O_mux_dq_oe,
    output wire [3:0]   O_mux_dqm
);
//------------------------Parameter----------------------
`include "sdram_common.vh" 

// fsm
localparam [4:0]
    IDLE    = 0,
    ACT0    = 1,
    NOP0    = 2,
    ACT1    = 3,
    NOP1    = 4,
    ACT2    = 5,
    READ0   = 6,
    READ1   = 7,
    READ2   = 8,
    NOP2    = 9,
    PRE     = 10,
    WAIT    = 11,
    LOOP    = 12,
    V_ACT0  = 13,
    V_NOP0  = 14,
    V_ACT1  = 15,
    V_NOP1  = 16,
    V_ACT2  = 17,
    V_NOP2  = 18,
    V_ACT3  = 19,
    V_READ0 = 22,
    V_READ1 = 23,
    V_READ2 = 24,
    V_READ3 = 25,
    V_NOP3  = 26,
    V_PRE   = 27,
    V_WAIT  = 28,
    V_LOOP  = 29;

// box direction
localparam [1:0]
    LANDSCAPE = 0, // 横向
    PORTRAIT0 = 1, // 纵向，第一个端口在左侧
    PORTRAIT1 = 2; // 纵向，第一个端口在右侧

localparam
    RL = 4 + SD_MR_CL; // read data latency

//------------------------Local signal-------------------
// read request (@I_sclk)
reg         busy;
reg         req_sr;
reg  [2:0]  done_sr;
reg  [1:0]  read_buf_sel;
reg  [3:0]  read_bit_sel;
reg  [4:0]  read_scan_id;
reg  [4:0]  read_port_max;
reg  [8:0]  read_pixel_id;
reg  [9:0]  read_pixel_count;
reg  [7:0]  read_ram_addr;
reg         scan_q_valid;

// fsm (@I_sdram_clk)
reg  [4:0]  state;
reg  [4:0]  next;
wire        landscape_mode;
reg         first_read;
reg  [3:0]  start_sr;
reg  [1:0]  over_sr;
reg  [2:0]  delay_counter;
reg  [2:0]  port_id; // 高3bit
reg         last_port;
reg         change_row;
reg  [5:0]  pixel_id;
reg  [6:0]  pixel_cnt;
reg         last_pixel;

// sdram mux
reg         mux_req;
reg  [3:0]  mux_cmd;
reg  [1:0]  mux_ba;
reg  [18:0] sdram_addr;
reg  [10:0] mux_addr;

// data repack
reg  [31:0] d;
reg  [7:0]  r;
reg  [7:0]  g;
reg  [7:0]  b;
reg  [RL:0] read_sr;
reg  [2:0]  word_sel;
reg  [23:0] word0;
reg  [23:0] word1;
wire [23:0] word2;
reg  [71:0] data_buf;
reg  [1:0]  data_valid;
reg  [2:0]  port_index;

// ram interface @(I_sdram_clk)
reg  [7:0]  ram_sel;
reg  [7:0]  ram_addr;

// portrait
reg  [2:0]  v_port_loop;
reg         v_change_row;
reg  [7:0]  v_pixel_cnt;
reg         v_last_pixel;
reg  [4:0]  v_port_id;
reg  [8:0]  v_pixel_id;

// portrait repack
reg  [RL:0]  v_read_sr;
reg  [3:0]   v_word_sel;
reg  [23:0]  v_word0;
reg  [23:0]  v_word1;
reg  [23:0]  v_word2;
wire [23:0]  v_word3;
wire [95:0]  v_data_pack;
reg  [119:0] v_data_buf;
reg  [3:0]   v_port_num;
reg  [31:0]  v_port_mask;
reg  [2:0]   v_port_loop1;
reg          v_ram_wren;
reg  [7:0]   v_ram_sel;
reg  [7:0]   v_ram_addr;
reg  [35:0]  v_ram_data;

//------------------------Instantiation------------------

//------------------------Body---------------------------
//{{{+++++++++++++++++++++read request+++++++++++++++++++
assign O_read_busy = busy;

// busy
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        busy <= 1'b0;
    else if (~busy && I_read_req)
        busy <= 1'b1;
    else if (done_sr[2:1] == 2'b01)
        busy <= 1'b0;
end

// req_sr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        req_sr <= 1'b0;
    else if (~busy && I_read_req)
        req_sr <= 1'b1;
    else
        req_sr <= 1'b0;
end

// done_sr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        done_sr <= 1'b0;
    else
        done_sr <= {done_sr[1:0], over_sr[1]};
end

// command buffer
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n) begin
        read_buf_sel     <= 1'b0;
        read_bit_sel     <= 1'b0;
        read_port_max    <= 1'b0;
        read_pixel_id    <= 1'b0;
        read_pixel_count <= 1'b0;
        read_ram_addr    <= 1'b0;
    end
    else if (I_read_req && !busy) begin
        read_buf_sel     <= I_read_buf_sel;
        read_bit_sel     <= I_read_bit_sel;
        read_port_max    <= I_read_port_max;
        read_pixel_id    <= I_read_pixel_id;
        read_pixel_count <= I_read_pixel_count;
        read_ram_addr    <= I_read_ram_addr;
    end
end

assign O_scan_map_rden = I_read_req && !O_read_busy;
assign O_scan_map_addr = I_read_scan_id;

// scan_q_valid
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        scan_q_valid <= 1'b0;
    else
        scan_q_valid <= O_scan_map_rden;
end

// read_scan_id
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        read_scan_id <= 1'b0;
    else if (scan_q_valid)
        read_scan_id <= I_scan_map_q;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++fsm++++++++++++++++++++++++++++
assign landscape_mode = (I_cfg_box_dir == LANDSCAPE);

// state
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        state <= IDLE;
    else
        state <= next;
end

// next
always @(*) begin
    case (state)
        IDLE: begin
            if (!start_sr[3])
                next = IDLE;
            else if (landscape_mode)
                next = ACT0;
            else
                next = V_ACT0;
        end

        ACT0: begin
            next = NOP0;
        end

        NOP0: begin
            if (I_mux_ack)
                next = ACT1;
            else
                next = NOP0;
        end

        ACT1: begin
            next = READ0;
        end

        ACT2: begin
            next = NOP1;
        end

        NOP1: begin
            next = READ1;
        end

        READ0: begin
            if (first_read)
                next = ACT2;
            else
                next = READ1;
        end

        READ1: begin
            next = READ2;
        end

        READ2: begin
            if (~last_port)
                next = READ0;
            else if (change_row | last_pixel)
                next = NOP2;
            else
                next = READ0;
        end

        NOP2: begin
            next = PRE;
        end

        PRE: begin
            next = WAIT;
        end

        WAIT: begin
            if (delay_counter >= DELAY_RP)
                next = LOOP;
            else
                next = WAIT;
        end

        LOOP: begin
            if (last_pixel)
                next = IDLE;
            else
                next = ACT0;
        end

        V_ACT0: begin
            next = V_NOP0;
        end

        V_NOP0: begin
            if (I_mux_ack)
                next = V_ACT1;
            else
                next = V_NOP0;
        end

        V_ACT1: begin
            next = V_NOP1;
        end

        V_NOP1: begin
            next = V_ACT2;
        end

        V_ACT2: begin
            next = V_NOP2;
        end

        V_NOP2: begin
            next = V_ACT3;
        end

        V_ACT3: begin
            next = V_READ0;
        end

        V_READ0: begin
            next = V_READ1;
        end

        V_READ1: begin
            next = V_READ2;
        end

        V_READ2: begin
            next = V_READ3;
        end

        V_READ3: begin
            if (v_port_loop != 1'b1)
                next = V_READ0;
            else if (v_change_row || v_last_pixel)
                next = V_NOP3;
            else
                next = V_READ0;
        end

        V_NOP3: begin
            next = V_PRE;
        end

        V_PRE: begin
            next = V_WAIT;
        end

        V_WAIT: begin
            if (delay_counter >= DELAY_RP)
                next = V_LOOP;
            else
                next = V_WAIT;
        end

        V_LOOP: begin
            if (v_last_pixel)
                next = IDLE;
            else
                next = V_ACT0;
        end

        default: begin
            next = IDLE;
        end
    endcase
end

// first_read
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        first_read <= 1'b0;
    else if (state == ACT0)
        first_read <= 1'b1;
    else if (state == READ0)
        first_read <= 1'b0;
end

// start_sr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        start_sr <= 1'b0;
    else if (~busy && I_read_req || req_sr)
        start_sr <= {start_sr[2:0], 1'b1};
    else
        start_sr <= {start_sr[2:0], 1'b0};
end

// over_sr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        over_sr <= 1'b0;
    else if (state == LOOP && next == IDLE)
        over_sr <= 2'b11;
    else if (state == V_LOOP && next == IDLE)
        over_sr <= 2'b11;
    else
        over_sr <= over_sr << 1;
end

// delay_counter
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        delay_counter <= 1'b0;
    else if (state == PRE || state == V_PRE)
        delay_counter <= 2'd2;
    else
        delay_counter <= delay_counter + 1'b1;
end

// port_id
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        port_id <= 1'b0;
    else if (state == IDLE)
        port_id <= 1'b0;
    else if (last_port)
        port_id <= 1'b0;
    else if (state == READ2)
        port_id <= port_id + 1'b1;
end

// last_port
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        last_port <= 1'b0;
    else if (state == READ1 && port_id == read_port_max[4:2])
        last_port <= 1'b1;
    else
        last_port <= 1'b0;
end

// change_row
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        change_row <= 1'b0;
    else if (state == READ1)
        change_row <= (pixel_id[2:0] == 3'b111);
    else
        change_row <= 1'b0;
end

// pixel_id
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        pixel_id <= 1'b0;
    else if (state == IDLE && next == ACT0)
        pixel_id <= read_pixel_id[8:3];
    else if (last_port)
        pixel_id <= pixel_id + 1'b1;
end

// pixel_cnt
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        pixel_cnt <= 1'b0;
    else if (state == IDLE && next == ACT0)
        pixel_cnt <= read_pixel_count[9:3];
    else if (last_port)
        pixel_cnt <= pixel_cnt - 1'b1;
end

// last_pixel
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        last_pixel <= 1'b0;
    else if (state == IDLE)
        last_pixel <= 1'b0;
    else if (state == READ1 && pixel_cnt == 1'b1)
        last_pixel <= 1'b1;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++sdram mux++++++++++++++++++++++
assign O_mux_req    = mux_req;
assign O_mux_cs_n   = mux_cmd[3];
assign O_mux_ras_n  = mux_cmd[2];
assign O_mux_cas_n  = mux_cmd[1];
assign O_mux_we_n   = mux_cmd[0];
assign O_mux_ba     = mux_ba;
assign O_mux_addr   = mux_addr;
assign O_mux_dq_out = 1'b0;
assign O_mux_dq_oe  = 1'b0;
assign O_mux_dqm    = 1'b0;

// mux_req
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_req <= 1'b0;
    else if (state == ACT0 || state == V_ACT0)
        mux_req <= 1'b1;
    else if (state == LOOP || state == V_LOOP)
        mux_req <= 1'b0;
end

// mux_cmd
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_cmd <= SD_CMD_NOP;
    else if (state == ACT0 || state == ACT1 || state == ACT2 || state == V_ACT0
        || state == V_ACT1 || state == V_ACT2 || state == V_ACT3)
        mux_cmd <= SD_CMD_ACT;
    else if (state == NOP0 || state == V_NOP0) begin
        if (I_mux_ack)
            mux_cmd <= SD_CMD_NOP;
        //else
        //    mux_cmd <= SD_CMD_ACT;
    end
    else if (state == READ0 || state == READ1 || state == READ2 || state == V_READ0
        || state == V_READ1 || state == V_READ2 || state == V_READ3)
        mux_cmd <= SD_CMD_READ;
    else if (state == PRE || state == V_PRE)
        mux_cmd <= SD_CMD_PRE;
    else
        mux_cmd <= SD_CMD_NOP;
end

// mux_ba
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_ba <= 1'b0;
    else if (state == ACT0 || state == READ0)
        mux_ba <= read_bit_sel[3:2] + 2'd0;
    else if (state == ACT1 || state == READ1)
        mux_ba <= read_bit_sel[3:2] + 2'd1;
    else if (state == ACT2 || state == READ2)
        mux_ba <= read_bit_sel[3:2] + 2'd2;
    else if (state == V_ACT0 || state == V_READ0)
        mux_ba <= read_bit_sel[3:2] + 2'd0;
    else if (state == V_ACT1 || state == V_READ1)
        mux_ba <= read_bit_sel[3:2] + 2'd1;
    else if (state == V_ACT2 || state == V_READ2)
        mux_ba <= read_bit_sel[3:2] + 2'd2;
    else if (state == V_ACT3 || state == V_READ3)
        mux_ba <= read_bit_sel[3:2] + 2'd3;
end

// mux_addr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_addr <= 1'b0;
    else if (state == ACT0 || state == ACT1 || state == ACT2) begin
        if (I_cfg_scan_mode == 1'b0)
            mux_addr <= {read_buf_sel, read_bit_sel[3:2], read_scan_id[3:0], pixel_id[5:3]};
        else                                                
            mux_addr <= {read_buf_sel, read_bit_sel[3:2], read_scan_id[4:0], pixel_id[4:3]};
    end
    else if (state == READ0 || state == READ1 || state == READ2)
        mux_addr <= {pixel_id[2:0], port_id, read_bit_sel[1:0]};
    else if (state == V_ACT0 || state == V_ACT1 || state == V_ACT2) begin
        if (I_cfg_scan_mode == 1'b0)
            mux_addr <= {read_buf_sel, read_bit_sel[3:2], read_scan_id[3:0], v_pixel_id[8:6]};
        else                                                
            mux_addr <= {read_buf_sel, read_bit_sel[3:2], read_scan_id[4:0], v_pixel_id[7:6]};
    end
    else if (state == V_READ0 || state == V_READ1 || state == V_READ2 || state == V_READ3)
        mux_addr <= {v_pixel_id[5:3], v_port_id[4:3], v_pixel_id[2], read_bit_sel[1:0]};
    else if (state == PRE || state == V_PRE)
        mux_addr <= 11'h400;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++data repack++++++++++++++++++++
// read_sr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        read_sr <= 1'b0;
    else if (state == READ0 || state == READ1 || state == READ2)
        read_sr <= {read_sr[RL-1:0], 1'b1};
    else
        read_sr <= {read_sr[RL-1:0], 1'b0};
end

// word_sel
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        word_sel <= 3'b001;
    else if (state == IDLE && next == ACT0)
        word_sel <= 3'b001;
    else if (read_sr[RL])
        word_sel <= {word_sel[1:0], word_sel[2]};
end

// RGB重排序
assign word2 = {
    b[7], g[7], r[7],
    b[6], g[6], r[6],
    b[5], g[5], r[5],
    b[4], g[4], r[4],
    b[3], g[3], r[3],
    b[2], g[2], r[2],
    b[1], g[1], r[1],
    b[0], g[0], r[0]
};

//assign d = I_mux_dq_in;
always@(posedge I_sdram_clk)
    if(I_cfg_force_en==1)
    begin
        if(read_bit_sel==I_cfg_force_bit-1)
            d <= 32'hFFFF_FFFF;
        else
            d <= 32'b0;
    end
    else
        d <= I_mux_dq_in;

// r
always @(*) begin
    if (I_cfg_color_sel[1:0] == 2'd0)
        r = {d[28], d[24], d[20], d[16], d[12], d[8], d[4], d[0]};
    else if (I_cfg_color_sel[1:0] == 2'd1)
        r = {d[29], d[25], d[21], d[17], d[13], d[9], d[5], d[1]};
    else
        r = {d[30], d[26], d[22], d[18], d[14], d[10], d[6], d[2]};
end

// g
always @(*) begin
    if (I_cfg_color_sel[3:2] == 2'd0)
        g = {d[28], d[24], d[20], d[16], d[12], d[8], d[4], d[0]};
    else if (I_cfg_color_sel[3:2] == 2'd1)
        g = {d[29], d[25], d[21], d[17], d[13], d[9], d[5], d[1]};
    else
        g = {d[30], d[26], d[22], d[18], d[14], d[10], d[6], d[2]};
end

// b
always @(*) begin
    if (I_cfg_color_sel[5:4] == 2'd0)
        b = {d[28], d[24], d[20], d[16], d[12], d[8], d[4], d[0]};
    else if (I_cfg_color_sel[5:4] == 2'd1)
        b = {d[29], d[25], d[21], d[17], d[13], d[9], d[5], d[1]};
    else
        b = {d[30], d[26], d[22], d[18], d[14], d[10], d[6], d[2]};
end

// word0
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        word0 <= 1'b0;
    else if (read_sr[RL] && word_sel[0])
        word0 <= word2;
end

// word1
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        word1 <= 1'b0;
    else if (read_sr[RL] && word_sel[1])
        word1 <= word2;
end

// data_buf
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        data_buf <= 1'b0;
    else if (read_sr[RL] && word_sel[2])
        data_buf <= {
            word2[23:21],
            word1[23:21],
            word0[23:21],
            word2[20:18],
            word1[20:18],
            word0[20:18],
            word2[17:15],
            word1[17:15],
            word0[17:15],
            word2[14:12],
            word1[14:12],
            word0[14:12],
            word2[11:9],
            word1[11:9],
            word0[11:9],
            word2[8:6],
            word1[8:6],
            word0[8:6],
            word2[5:3],
            word1[5:3],
            word0[5:3],
            word2[2:0],
            word1[2:0],
            word0[2:0]
        };
    else
        data_buf <= data_buf >> 36;
end

// data_valid
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        data_valid <= 1'b0;
    else if (read_sr[RL] && word_sel[2])
        data_valid <= 2'b11;
    else
        data_valid <= data_valid >> 1;
end

// port_index
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        port_index <= 1'b0;
    else if (state == IDLE && next == ACT0)
        port_index <= 1'b0;
    else if (data_valid == 2'b01) begin
        if (port_index == read_port_max[4:2])
            port_index <= 1'b0;
        else
            port_index <= port_index + 1'b1;
    end
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++ram interface++++++++++++++++++
assign O_ram_wclk = I_sdram_clk;
assign O_ram_wren = landscape_mode?
                    (data_valid[0]? ram_sel : 1'b0) :
                    (v_ram_wren? v_ram_sel : 1'b0);
assign O_ram_addr = landscape_mode? ram_addr : v_ram_addr;
assign O_ram_data = landscape_mode? data_buf[35:0] : v_ram_data;

// ram_sel
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        ram_sel <= 1'b0;
    else
        ram_sel <= 8'd1 << port_index;
end

// ram_addr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        ram_addr <= 1'b0;
    else if (state == IDLE && next == ACT0)
        ram_addr <= read_ram_addr;
    else if (read_sr[RL] && word_sel[2])
        ram_addr[0] <= 1'b0;
    else if (data_valid == 2'b11)
        ram_addr[0] <= 1'b1;
    else if (data_valid == 2'b01 && port_index == read_port_max[4:2])
        ram_addr <= ram_addr + 1'b1;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++portrait+++++++++++++++++++++++
// v_port_loop
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_port_loop <= 1'b0;
    else if (state == V_ACT0)
        v_port_loop <= I_cfg_vport_num[5:3];
    else if (state == V_READ3) begin
        if (v_port_loop == 1'b1)
            v_port_loop <= I_cfg_vport_num[5:3];
        else
            v_port_loop <= v_port_loop - 1'b1;
    end
end

// v_change_row
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_change_row <= 1'b0;
    else if (state == IDLE && next == V_ACT0)
        v_change_row <= 1'b0;
    else if (state == V_READ0) begin
        if (v_pixel_id[5:2] == 4'b1111)
            v_change_row <= 1'b1;
        else
            v_change_row <= 1'b0;
    end
end

// v_pixel_cnt
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_pixel_cnt <= 1'b0;
    else if (state == IDLE && next == V_ACT0)
        v_pixel_cnt <= read_pixel_count[9:2];
    else if (state == V_READ3 && v_port_loop == 1'b1)
        v_pixel_cnt <= v_pixel_cnt - 1'b1;
end

// v_last_pixel
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_last_pixel <= 1'b0;
    else if (state == IDLE)
        v_last_pixel <= 1'b0;
    else if (state == V_READ0 && v_pixel_cnt == 1'b1)
        v_last_pixel <= 1'b1;
end

// v_port_id
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_port_id <= 1'b0;
    else if (state == V_ACT0)
        v_port_id <= 1'b0;
    else if (state == V_READ3) begin
        if (v_port_loop == 1'b1)
            v_port_id <= 1'b0;
        else
            v_port_id <= v_port_id + 4'd8;
    end
end

// v_pixel_id
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_pixel_id <= 1'b0;
    else if (state == IDLE && next == V_ACT0)
        v_pixel_id <= {read_pixel_id[8:3], 2'd0};
    else if (state == V_READ3 && v_port_loop == 1'b1)
        v_pixel_id <= v_pixel_id + 3'd4;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++portrait repack++++++++++++++++
// v_read_sr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        v_read_sr <= 1'b0;
    else if (state == V_READ0 || state == V_READ1 || state == V_READ2 || state == V_READ3)
        v_read_sr <= {v_read_sr[RL-1:0], 1'b1};
    else
        v_read_sr <= {v_read_sr[RL-1:0], 1'b0};
end

// v_word_sel
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_word_sel <= 4'b0001;
    else if (state == IDLE && next == V_ACT0)
        v_word_sel <= 4'b0001;
    else if (v_read_sr[RL])
        v_word_sel <= {v_word_sel[2:0], v_word_sel[3]};
end

// RGB重排序
assign v_word3 = {
    b[7], g[7], r[7],
    b[6], g[6], r[6],
    b[5], g[5], r[5],
    b[4], g[4], r[4],
    b[3], g[3], r[3],
    b[2], g[2], r[2],
    b[1], g[1], r[1],
    b[0], g[0], r[0]
};

// v_word0
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_word0 <= 1'b0;
    else if (v_read_sr[RL] && v_word_sel[0])
        v_word0 <= v_word3;
end

// v_word1
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_word1 <= 1'b0;
    else if (v_read_sr[RL] && v_word_sel[1])
        v_word1 <= v_word3;
end

// v_word2
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_word2 <= 1'b0;
    else if (v_read_sr[RL] && v_word_sel[2])
        v_word2 <= v_word3;
end

assign v_data_pack = {
    v_word3[23:21],
    v_word2[23:21],
    v_word1[23:21],
    v_word0[23:21],
    v_word3[20:18],
    v_word2[20:18],
    v_word1[20:18],
    v_word0[20:18],
    v_word3[17:15],
    v_word2[17:15],
    v_word1[17:15],
    v_word0[17:15],
    v_word3[14:12],
    v_word2[14:12],
    v_word1[14:12],
    v_word0[14:12],
    v_word3[11:9],
    v_word2[11:9],
    v_word1[11:9],
    v_word0[11:9],
    v_word3[8:6],
    v_word2[8:6],
    v_word1[8:6],
    v_word0[8:6],
    v_word3[5:3],
    v_word2[5:3],
    v_word1[5:3],
    v_word0[5:3],
    v_word3[2:0],
    v_word2[2:0],
    v_word1[2:0],
    v_word0[2:0]
};

// v_data_buf
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_data_buf <= 1'b0;
    else if (v_read_sr[RL] && v_word_sel[3]) begin
        if (v_port_num[1:0] == 1'b0)
            v_data_buf[95:0] <= v_data_pack;
        else if (v_port_num[1:0] == 1'b1)
            v_data_buf[107:12] <= v_data_pack;
        else
            v_data_buf[119:24] <= v_data_pack;
    end
    else if (v_port_num >= 2'd3)
        v_data_buf <= v_data_buf >> 36;
end

/* NOTE:
* 通过软件配置，保证条件1和条件2不同时满足
* 否逻辑过于复杂，timing难以保证
*/
// v_port_num
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_port_num <= 1'b0;
    else if (state == IDLE && next == V_ACT0)
        v_port_num <= 1'b0;
    else if (v_read_sr[RL] && v_word_sel[3]) begin // 条件1
        if (v_port_mask[7])
            v_port_num <= v_port_num + 4'd8;
        else if (v_port_mask[6])
            v_port_num <= v_port_num + 4'd7;
        else if (v_port_mask[5])
            v_port_num <= v_port_num + 4'd6;
        else if (v_port_mask[4])
            v_port_num <= v_port_num + 4'd5;
        else if (v_port_mask[3])
            v_port_num <= v_port_num + 4'd4;
        else if (v_port_mask[2])
            v_port_num <= v_port_num + 4'd3;
        else if (v_port_mask[1])
            v_port_num <= v_port_num + 4'd2;
        else if (v_port_mask[0])
            v_port_num <= v_port_num + 4'd1;
    end
    else if (v_port_num >= 2'd3) // 条件2
        v_port_num <= v_port_num - 2'd3;
end

// v_port_loop1
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_port_loop1 <= 1'b0;
    else if (state == IDLE && next == V_ACT0)
        v_port_loop1 <= I_cfg_vport_num[5:3];
    else if (v_read_sr[RL] && v_word_sel[3]) begin
        if (v_port_loop1 == 1'b1)
            v_port_loop1 <= I_cfg_vport_num[5:3];
        else
            v_port_loop1 <= v_port_loop1 - 1'b1;
    end
end

// v_port_mask
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_port_mask <= 1'b0;
    else if (state == IDLE && next == V_ACT0)
        v_port_mask <= I_cfg_vport_mask;
    else if (v_read_sr[RL] && v_word_sel[3]) begin
        if (v_port_loop1 == 1'b1)
            v_port_mask <= I_cfg_vport_mask;
        else
            v_port_mask <= v_port_mask >> 8;
    end
end

// v_ram_wren
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_ram_wren <= 1'b0;
    else if (v_port_num >= 2'd3)
        v_ram_wren <= 1'b1;
    else
        v_ram_wren <= 1'b0;
end

// v_ram_sel
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_ram_sel <= 1'b1;
    else if (v_ram_wren) begin
        if (v_ram_sel[read_port_max[4:2]])
            v_ram_sel <= 1'b1;
        else
            v_ram_sel <= v_ram_sel << 1;
    end
end

// v_ram_addr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_ram_addr <= 1'b0;
    else if (state == IDLE && next == V_ACT0)
        v_ram_addr <= read_ram_addr;
    else if (v_ram_wren && v_ram_sel[read_port_max[4:2]])
        v_ram_addr <= v_ram_addr + 1'b1;
end

// v_ram_data
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (!I_rst_n)
        v_ram_data <= 1'b0;
    else if (v_port_num >= 2'd3)
        v_ram_data <= {
            // pixel 3
            v_data_buf[35:33],
            v_data_buf[23:21],
            v_data_buf[11:9],
            // pixel 2
            v_data_buf[32:30],
            v_data_buf[20:18],
            v_data_buf[8:6],
            // pixel 1
            v_data_buf[29:27],
            v_data_buf[17:15],
            v_data_buf[5:3],
            // pixel 0
            v_data_buf[26:24],
            v_data_buf[14:12],
            v_data_buf[2:0]
        };
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

endmodule

`default_nettype wire

// vim:set ts=4 sw=4 et fenc=utf-8 fdm=marker:
